/*******************************
 *  Редактор
 ******************************/
var tsc_editor=function(config){
	/*********
	 *  VARS INIT
	 ********/
  this.version='0.0.1';
	//this.debug=true;	//Режим отладки
	//this.trace=1;     //Что трассировать
	this.current={
	  frame:false,
	  layer:false,
	  frameNum:false,
	  layerNum:false
	};
	this.errors=[];
	
	this.iface={
		tools:false,
		screen:false,
    canvas:false,
		timeline:false,
		cursor:false,
    mousecatch:false,
    keycatch:false
	};

	this.config={
	  screenId:'ts_screen',
	  timelineId:'ts_timeline',
	  toolsId:'ts_tools',
	  on:[
	    ''
		]
	};
	
	/*
	//Текущий инструмент
	this.tool='drawer';
	//Поддердживаемый набор инструментов с параметрами и значениями по умолчанию
	this.tools={
	  drawer:{
	    size:1,
	    brush:'default',
	    color:'#000',
	    opacity:1
		},
		eraser:{
		  size:1,
		  brush:'default'
		},
		zoomer:{
		  mode:'plus'
		},
		filler:{
		  color:'#f00'
		}
	};
	//Доступные браши
	this.brushes={
	  'default':{
	    //Инициализация кисти - установка параметров
	    construct:function(params){},
	    //Инициализация действия (провередения линии например)
	    init_sroke:function(x,y){},
	    //Драфтовая прорисовка линии на системном канвасе - передается текущий x,y и набранный набор pointStack
	    draft:function(x,y,pointStack){
	      this.syscanva.lineTo(y, y);
				this.syscanva.stroke();
			},
			//Чистовая прорисовка линии на реальном канвасе - передаются pointStack
	    draw:function(pointStack){
	      this.canva.lineTo(y, y);
				this.canva.stroke();
			}
		}
	};
	*/
	
	/*********
	 *  Base editor init - create controls and tools DOM elements and init them
	 ********/
	this.init=function(config){
	  if(typeof(config)!='undefined'){
	    for(var i in config) this.config[i]=config[i];
		}
		
	  this.iface.screen=new tsc_screen(this);
	  this.iface.timeline=new tsc_timeline(this);
	  
	  for(var evt in this.config.on){
	    if( typeof(this.config.on[evt])=='function' )
				this.on( evt, this.config.on[evt] );
		}
		
		this.on('timelineChange',function(prm,obj){
		  obj.project.apply();
		});
	}

	/********************
	 *  PROJECT sub object
	 *  Инкапсуляция работы с проектом
	 **/
	this.project=new function(editor){
	  this.data={};
	  this.editor=editor;
	  
		this.project_def={
	    name:		'project_'+ ( Math.floor(Math.random()*1000000) ),
	    date:		new Date(),
	    width:	'550',
	    height:	'440',
	    //Кадры могут меняться местами вне зависимости от порядка создания
	    frameOrd:	[0],
	    //Слои могут меняться местами вне зависимости от порядка создания
	    layerOrd: [0],
	    layerVis: [1],
			//table[frame][level] (table[1][0]- второй кадр первого слоя)
			table:[],
			//actions list grouped by canvases
			action:[
			  //canvas 0
			  //[{},{}],
				//canvas 1
				//[{},{}]
			]
		};
		
		/**************
		 *  set - Set project params
		 **************/
		this.set=function(proj){
			var dt = new Date();
			var pdt=	dt.getFullYear()	+'-'+
								( dt.getMonth() + 1 ) +'-'+
								dt.getDate() +' '+
								dt.getHours() +':'+
								dt.getMinutes() +':'+
								dt.getSeconds();
			this.project_def.date=pdt;
			

			if(typeof(proj)=='undefined'){
				proj=this.project_def;
			}else{
				for(var i in this.project_def)
					if( typeof( proj[i] )=='undefined' || !proj[i])
						proj[i]=this.project_def[i];
				if(!this.valid(proj)) return false;
			}
		  this.data=proj;
		  return  this.init();
		}
		
		this.load=function(){}
		
		/**************
		 *  valid валидация проекта в параметре
		 **************/
		this.valid=function(proj){
		  //Проверка обязательных полей
		  var c=['name','date', 'width','height','frameOrd','layerOrd','table','action'];
			for(var i=0; i<c.length; i++ )	if(typeof( proj[ c[i] ] )=='undefined') return false;
			//Сопоставление кол-ва фреймов в order и table
			if( proj.table.length>proj.frameOrd.length ) return false;
			//...
			//Сопоставление кол-ва слоев в order и table
			//...
		  return true;
		}
		
		
		/**************
		 *  init - init editor vars for proj
		 *  and call editor's controls and tools init project methods
		 **************/
		this.init=function(){
			this.apply();
		  
		  //init screen - создает реальные объекты канвы и делает свое.
		  if(!this.editor.iface.screen.init_project()) return false;
		  //init timeline
		  if(!this.editor.iface.timeline.init_project()) return false;
		  return true;
		}
		
		this.apply=function(){
//console.log('frameOrd:',this.data.frameOrd, 'layerOrd:',this.data.layerOrd,'table:'+this.data.table+' canvas:',this.editor.iface.screen.canvas);
		  //Заполнить таблицу кадров и слоев в порядке frameOrd и layerOrd идентификаторами канвасов
		  for(var fnum=0,cnt=this.data.frameOrd.length; fnum<cnt; fnum++ ){
		    for(var lnum=0,jcnt=this.data.layerOrd.length; lnum<jcnt; lnum++ ){
					//Если фрейма нет - добавить
					if( typeof(this.data.table[fnum])=='undefined' ) this.data.table[fnum]=new Array();
					//если ячейка [фрейм/слой] не заполнена
		      if( typeof(this.data.table[fnum][lnum])=='undefined'){
						//Создать новый канвас и записать его ID
						var t=this.editor.iface.screen.create_canvas();
						if(!t){
						  err('create canvas error');
						  return false;
						}
						this.data.table[fnum][lnum]=t.id;

					//Иначе - вызвать создание канваса с известным ID
					}else{
					  if(!this.editor.iface.screen.create_canvas(this.data.table[fnum][lnum])){
						  err('create canvas error');
						  return false;
						}
					}
				}
		  }
		
		  if(!this.editor.iface.screen.apply()) return false;
		  if(!this.editor.iface.timeline.apply()) return false;
		  return true;
		}
	}(this);

	this.frames=new function(parent){
	  this.editor=parent;
		/**************
		 *  add frame method after order num
		 *************/
	  this.add=function(afterNum){
		  if( typeof(afterNum)=='undefined' ){
		    if(this.editor.current.frameNum!==false){
		      afterNum=this.editor.current.frameNum;
				}else
		    	afterNum=this.editor.project.data.frameOrd.length-1;
			}	else
				afterNum=parseInt(afterNum);

			//Если пытаются добавить слишком далеко - подправить
		  if( afterNum > this.editor.project.data.frameOrd.length-1 ) afterNum = this.editor.project.data.frameOrd.length-1;
		  //Detect frameId and frameNum
		  //var frameId=this.editor.project.data.frameOrd.max()+1;
		  //console.log('Screen canvas before:',this.editor.iface.screen.canvas);
		  if(!this.editor.iface.screen.create_canvas()){
		    err('create canvas');
		    return false;
			}

		  var frameId= this.editor.iface.screen.canvas[ this.editor.iface.screen.canvas.length-1 ].id;
		  var frameNum=afterNum +1;

		  //add record in index
		  if( this.editor.project.data.frameOrd.length > frameNum ){
		    tmpParts={
		      before:	this.editor.project.data.frameOrd.slice(0, frameNum ),
		      after:	this.editor.project.data.frameOrd.slice(frameNum ),
				}

				this.editor.project.data.frameOrd=tmpParts.before.concat(
				  [frameId], tmpParts.after
				);
				delete tmpParts;
			}else
			  this.editor.project.data.frameOrd[frameNum]=frameId;

			this.editor.trigger('timelineChange', {type:'add_frame','frameId':frameId, 'frameNum':frameNum } );
			return true;
		}
		
		/**************
		 *  remove frame method after order num
		 *************/
		this.remove=function(frameNum){
		  if( typeof(frameNum)=='undefined' ){
		    if(this.editor.current.frameNum!==false){ frameNum=this.editor.current.frameNum; }
			}
		  if( typeof(frameNum)!='number' )
				return this.editor.err( 'remove frame params err: expected number, passed '+typeof(frameNum) );
		  if( typeof( this.editor.project.data.frameOrd[frameNum] )=='undefined' )
		    return this.editor.err( 'remove frame params err: no index'+ frameNum +' in frameOrd' );
			if(this.editor.project.data.frameOrd.length==1) return false;

			//Remove from screen
			if(!this.editor.iface.screen.remove_frame(frameNum)) return this.editor.err('remove frame (screen)');

		  var frameId=this.editor.project.data.frameOrd[frameNum];

		  //remove from table
			if( this.editor.project.data.table[frameId] )
				//delete this.editor.project.data.table[frameId];
				this.editor.project.data.table[frameId]=false;

		  //remove from in index
		  this.editor.project.data.frameOrd.remove( frameNum );

			this.editor.trigger('timelineChange', {type:'remove_frame','frameId':frameId, 'frameNum':frameNum } );
			
			var nf=(this.editor.current.frameNum>0)?this.editor.current.frameNum-1:this.editor.current.frameNum;
			this.editor.get_canvas(nf, this.editor.current.layerNum);
			
			return true;
		}
		
		/***************
		 *  Moving frames diapazon fromNum,toNum to position newPos
		 **************/
		this.move=function( fromNum,toNum, newPos){
		  //validation
		  if(typeof(newPos)!='number'){ newPos=toNum; toNum=fromNum;}
		  if( newPos>=fromNum && newPos <=toNum ) return false;
		  t=[  fromNum,toNum, newPos ];
		  for(var i=0; i<3; i++ ){
		    if( typeof(t[i])!='number' ) return false;
		    if( t[i]<0 || t[i]>(this.editor.project.data.frameOrd.length-1) ) return false;
			}
			delete t;

	   	var mov1=this.editor.project.data.frameOrd.slice(0,fromNum);
		  var mov2=this.editor.project.data.frameOrd.slice(fromNum,toNum+1);
		  var mov3=this.editor.project.data.frameOrd.slice(toNum+1);
		  this.editor.project.data.frameOrd=mov1.concat(mov3);
		  if( newPos > toNum ) newPos-=mov2.length-1;

		  var a1=this.editor.project.data.frameOrd.slice(0,newPos);
		  var a2=this.editor.project.data.frameOrd.slice(newPos);
		  this.editor.project.data.frameOrd=a1.concat( mov2,a2 );
	    this.editor.trigger('timelineChange', {type:'move_frame', 'fromNum':fromNum, 'toNum':toNum, 'newPos':newPos} );
			return true;
		}
	}(this);
	//this.add_frame=function(afterNum){}
	//this.remove_frame=function(frameNum){
	//this.move_frames=function( fromNum,toNum, newPos)


	
	this.layers=new function(parent){
		  this.editor=parent;
		  /**************
			 *  add layer method after order num
			 *************/
		  this.add=function(afterNum){
				if( typeof(afterNum)=='undefined' ){
			    if(this.editor.current.layerNum!==false){
			      afterNum=this.editor.current.layerNum;
					}else
			    	afterNum=this.editor.project.data.layerOrd.length-1;
				}	else
					afterNum=parseInt(afterNum);
		  
				if(typeof(afterNum)!='number') return false;
				
				if( (this.editor.project.data.layerOrd.length-1)<afterNum ) afterNum=this.editor.project.data.layerOrd.length;
				if( afterNum<0 ) afterNum=0;

				var layerId=this.editor.project.data.layerOrd.max()+1;
				var layerNum=afterNum +1;

				//call screen creating frame
				//var tst=this.iface.screen.add_layer(layerId, layerNum);
				//if(!tst) return false;

			  //add record in index
			  if( this.editor.project.data.layerOrd.length > layerNum ){
			    tmpParts={
			      before:	this.editor.project.data.layerOrd.slice(0, layerNum ),
			      after:	this.editor.project.data.layerOrd.slice(layerNum ),
					}
					this.editor.project.data.layerOrd=tmpParts.before.concat(
					  [layerId], tmpParts.after
					);
					delete tmpParts;
				}else
				  this.editor.project.data.layerOrd[layerNum]=layerId;

				this.editor.trigger('timelineChange', {type:'add_layer','layerId':layerId, 'layerNum':layerNum } );
				return true;
			}
			
			this.remove==function(layerNum){
			  if( typeof(layerNum)=='undefined' ){
			    if(this.editor.current.layerNum!==false){ layerNum=this.editor.current.layerNum; }
				}
				
			  if( typeof(layerNum)!='number' )
					return this.err( 'remove layer params err: expected number, passed '+typeof(layerNum) );
			  if( typeof( this.editor.project.data.layerOrd[layerNum] )=='undefined' )
			    return this.err( 'remove layer params err: no index'+ layerNum +' in layerOrd' );
				if(this.editor.project.data.layerOrd.length==1) return false;

				//Remove from screen
				if(!this.editor.iface.screen.remove_layer(layerNum)) return this.editor.err('remove layer (screen)');

			  var layerId=this.editor.project.data.layerOrd[layerNum];

			  //remove from table
			  for(var i=0; i<this.editor.project.data.table.length; i++)
			    //delete this.editor.project.data.table[i][layerId];
			    this.editor.project.data.table[i][layerId]=false;
				
			  //remove from in index
			  this.editor.project.data.layerOrd.remove( layerNum );
				
				this.editor.trigger('timelineChange', {type:'remove_layer','layerId':layerId, 'layerNum':layerNum } );

				var nl=(this.editor.current.layerNum>0)?this.editor.current.layerNum-1:this.editor.current.layerNum;
				this.editor.get_canvas(this.editor.current.frameNum, nl);
				return true;
			}
			
			this.move=function( fromNum,toNum, newPos){
			  if(typeof(newPos)!='number'){ newPos=toNum; toNum=fromNum;}
			  if( newPos>=fromNum && newPos <=toNum ) return false;
			  t=[  fromNum,toNum, newPos ];
			  for(var i=0; i<3; i++ ){
			    if( typeof(t[i])!='number' ) return false;
			    if( t[i]<0 || t[i]>(this.editor.project.data.layerOrd.length-1) ) return false;
				}
				delete t;
		   	var mov1=this.editor.project.data.layerOrd.slice(0,fromNum);
			  var mov2=this.editor.project.data.layerOrd.slice(fromNum,toNum+1);
			  var mov3=this.editor.project.data.layerOrd.slice(toNum+1);
			  this.editor.project.data.layerOrd=mov1.concat(mov3);
			  if( newPos > toNum ) newPos-=mov2.length-1;

			  var a1=this.editor.project.data.layerOrd.slice(0,newPos);
			  var a2=this.editor.project.data.layerOrd.slice(newPos);
			  this.editor.project.data.layerOrd=a1.concat( mov2,a2 );
		    this.trigger('timelineChange', {type:'move_layer', 'fromNum':fromNum, 'toNum':toNum, 'newPos':newPos} );
				return true;
			}
			this.visibility=function(layerNum,visible){
			  if( typeof(layerNum)=='undefined' ) return false;
			  if( typeof( this.editor.project.data.layerVis[ layerNum ] )=='undefined' ) this.editor.project.data.layerVis[ layerNum ]=true;
			  
			  if( typeof(visible)=='undefined' ){
				//Если без значения - вернуть тек. сост.
			    return this.editor.project.data.layerVis[ layerNum ];
				}else{
				//со значением - выставить значение
				  var val=(visible)?true:false;
				  if(this.editor.project.data.layerVis[ layerNum ]!=val){
				  	this.editor.project.data.layerVis[ layerNum ]=val;
				  	this.editor.trigger('timelineChange', {type:'layer_visibility', 'layerNum':layerNum, 'visible':visible} );
				  }
				}
				return this.editor.project.data.layerVis[ layerNum ];
			}
	}(this);
	//this.add_layer
	//this.remove_layer
	//this.move_layers
	
	this.get_canvas=function(frameNum,layerNum,mode){
	  if( typeof(mode)=='undefined' ) mode='ctx';
	  var fid=this.project.data.frameOrd[frameNum];
	  var lid=this.project.data.layerOrd[layerNum];
console.log('Ord:',this.project.data.frameOrd, this.project.data.layerOrd)
console.log('IDs:',fid,lid);
console.log('Table:',this.project.data.table);

	  if( typeof(this.project.data.table[fid])=='undefined' ||
				typeof(this.project.data.table[fid][lid])=='undefined'
		){
				err('frame frameNum:'+frameNum+',layerNum:'+layerNum+' ('+fid+','+lid+') not found');
				return false;
		}
	    

		//Если не видимый- показать
		this.layers.visibility(layerNum,1);

    //Получить выбранный кадр
		var ctx=this.iface.screen.get_canvas( this.project.data.table[fid][lid],mode );
		if(!ctx){
		  err('cant get canvas (id:'+this.project.data.table[fid][lid]+')');
		  return false;
		}
		
	  //Показать всю колонку
  	this.iface.screen.show_canvas_col(frameNum);
  	
		this.current.frame=fid;this.current.layer=lid;
		this.current.frameNum=frameNum;this.current.layerNum=layerNum;
    this.trigger('cellset', {frame:frameNum,layer:layerNum} );
	  return ctx;
	}
	
	/**************
	 *  HELP functions
	 *************/
	this.err=function(txt){
	  this.errors.push(txt);
	  err(txt);
	  return false;
	}
	
	this.get_errors=function(){
	  return this.errors.concat(this.iface.screen.errors);
	}

	/****************************************************************************
	 * EVENT Methods
	 ***************************************************************************/
  /*****************************************
   * Подписка на событие
   * Использование:
   *  menu.on('select', function(item) { ... }
	 ***************************************/
	  
	  this.on = function(eventName, handler) {
	    if (!this._eventHandlers) this._eventHandlers = [];
	    if (!this._eventHandlers[eventName]) {
	      this._eventHandlers[eventName] = [];
	    }
	    this._eventHandlers[eventName].push(handler);
	  };

	  /**
	   * Прекращение подписки
	   *  menu.off('select',  [handler])
	   */
	  this.off = function(eventName, handler){
	    var handlers = this._eventHandlers[eventName];
	    if (!handlers) return;
	    switch(typeof(handler)){
	      case 'function':
					for(var i=0; i<handlers.length; i++) {
			      if (handlers[i] == handler) {
			        handlers.splice(i--, 1);
			      }
			    }
			    break;
			  case 'undefined':
			    this._eventHandlers[eventName]=[];
			    break;
			}
	  };

	  /**
	   * Генерация события с передачей данных
	   *  this.trigger('select', item);
	   */
	  this.trigger = function(eventName) {
	    if(!this._eventHandlers || !this._eventHandlers[eventName]) {
	      return; // обработчиков для события нет
	    }
	    // вызвать обработчики
	    var handlers = this._eventHandlers[eventName];
	    for (var i = 0; i < handlers.length; i++) {
	      handlers[i].apply(this, [ [].slice.call(arguments, 1), this ] );
	    }
	  };
	/****************************************************************************
	 * CALL INIT method
	 ***************************************************************************/
		this.init(config);
}